﻿using System;
using System.Activities;
using System.Collections.Generic;
using Foulds.Calculator.StateMachine;
using Foulds.Calculator.StateMachine.Publisher;

namespace Foulds.Calculator.Service
{
    /// <auhtor>Hannes Foulds</auhtor>
    /// <summary>
    /// This class implements the calculator and uses a state machine workflow to handle the actual calculations.
    /// </summary>
    public class Calculator : ICalculator
    {
        public event DisplayChange DisplayChanged;

        #region Members
        /// <summary>The queue of keys that was pressed on the calculator.</summary>
        protected readonly Queue<CalculatorKey> KeyQueue = new Queue<CalculatorKey>();

        /// <summary>The workflow will publish changes though this listener.</summary>
        protected CalculatorPublisher CalculatorListener;

        /// <summary>The workflow application that will run the state machine workflow.</summary>
        protected WorkflowApplication WorkflowApplication;
        
        /// <summary>Indicator to show whether or not a key can be sent to the state machine.</summary>
        protected bool WaitingForKey;
        #endregion

        #region Properties

        #region Input Display
        /// <summary>
        /// The text that should be visible in the input field.
        /// </summary>
        public string InputDisplay
        {
            get
            {
                this.EnsurePower();
                return this.CalculatorListener.InputDisplay;
            }
        }
        #endregion

        #region Expression Display
        /// <summary>
        /// The text that should be shown as the expression entered.
        /// </summary>
        public string ExpressionDisplay
        {
            get
            {
                this.EnsurePower();
                return this.CalculatorListener.ExpressionDisplay;
            }
        }
        #endregion

        #endregion

        #region  Ensure Power
        /// <summary>
        /// This method is to ensure that the calculator has been turned on,
        /// if not throw an exception
        /// </summary>
        protected void EnsurePower()
        {
            if (this.WorkflowApplication == null)
                throw new ApplicationException("The Calculator is currently turned off.");
        }
        #endregion

        #region Power On
        /// <summary>
        /// The power on button of the calculator.
        /// This method initializes the workflow application.
        /// </summary>
        public void PowerOn()
        {
            // create the state listener and add it to the workflow parameters
            this.CalculatorListener = new CalculatorPublisher();
            this.CalculatorListener.DisplayChanged += CalculatorListenerDisplayChanged;
             
            Dictionary<string, object> workflowParameters = new Dictionary<string, object> { 
                { "StatePublisher", this.CalculatorListener } 
            };

            this.WorkflowApplication = new WorkflowApplication(new CalculatorStateMachine(), workflowParameters);
            this.WorkflowApplication.Idle += WorkflowIdle;
            this.WorkflowApplication.Run();
        }
        #endregion

        #region Power Off
        /// <summary>
        /// The power off button of the calculator.
        /// This method aborts the workflow.
        /// </summary>
        public void PowerOff()
        {
            this.WorkflowApplication.Abort();
        }
        #endregion

        #region Events

        #region Display Changed
        /// <summary>
        /// The display changed event.
        /// </summary>
        /// <param name="inputDisplay">The input text.</param>
        /// <param name="expressionDisplay">The expression text.</param>
        private void CalculatorListenerDisplayChanged(string inputDisplay, string expressionDisplay)
        {
            if (this.DisplayChanged != null)
                this.DisplayChanged(inputDisplay, expressionDisplay);
        }
        #endregion

        #region Workflow Idle
        /// <summary>
        /// The event handler that fires when the workflow becomes idle.
        /// </summary>
        private void WorkflowIdle(WorkflowApplicationIdleEventArgs workflowApplicationIdleEventArgs)
        {
            if (!this.ProcessQueue())
                this.WaitingForKey = true;
        }
        #endregion

        #endregion

        #region Press Key
        /// <summary>
        /// Send a key that was pressed to the calculator.
        /// </summary>
        /// <param name="key">The key that was pressed.</param>
        public void PressKey(CalculatorKey key)
        {
            this.KeyQueue.Enqueue(key);

            if (this.WaitingForKey)
                this.ProcessQueue();
        }
        #endregion

        #region Process Queue
        /// <summary>
        /// Send the fist key in the queue to the calculator state machine.
        /// </summary>
        protected bool ProcessQueue()
        {
            bool keyProcessed = false;

            if (this.KeyQueue.Count > 0)
            {
                this.WaitingForKey = false;
                keyProcessed = true;

                // send the key to the workflow
                this.WorkflowApplication.ResumeBookmark("CalculatorKey", this.KeyQueue.Dequeue());
            }

            return keyProcessed;
        }
        #endregion

    }
}
